package com.opennews.openplatform

import grails.compiler.GrailsCompileStatic
import grails.gorm.transactions.Transactional
import groovy.transform.CompileDynamic
import org.hibernate.criterion.CriteriaSpecification
import org.hibernate.sql.JoinType

@Transactional(readOnly = true)
@GrailsCompileStatic
class UserRoleService {
    /**
     * Queries role ids of user.
     * @param userId: The string of the user id.
     * @return The list of string of role id.
     */
    @CompileDynamic
    List<String> queryRoleIds(String accountGroupId, String userId) {
        return UserRole.createCriteria().list {
            // Sets the query condition.
            and {
                eq("accountGroup.id", accountGroupId)
                eq("user.id", userId)
            }

            // Specifies the property we need.
            projections {
                property("role.id", "id")
            }
        } as List<String>
    }

    /**
     * Queries roles' id, authoritym title based on provided user id against specific account group.
     * @param accountGroupId: The string of the account group id.
     * @param userId: The string of the user id.
     * @return The list of map instance which contains keys below.
     *          id: String of the role id.
     *          id: String of the role authority.
     *          title: String of the role title.
     */
    @CompileDynamic
    List<Map<String, String>> queryFullRoles(String accountGroupId, String userId) {
        // Queries the role list based on username.
        return UserRole.createCriteria().list {
            // Transforms the result data format to Map instance.
            resultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP)

            // Sets the join type for role property in order to get its data.
            createAlias("role", "role", JoinType.INNER_JOIN)

            // Sets the query condition.
            and {
                eq("accountGroup.id", accountGroupId)
                eq("user.id", userId)
            }

            // Specifies the property we need.
            projections {
                property("role.id", "id")
                property("role.authority", "authority")
                property("role.title", "title")
            }
        } as List<Map<String, String>>
    }

    /**
     * Check if the user with provided id has the specific role.
     * @param userId: The String of the user id.
     * @param roleName: The string of the role name used to check if the user has it.
     * @return The bool value. True means the user has the role. False means no.
     */
    @CompileDynamic
    Boolean hasRole(String accountGroupId, String userId, String roleName) {
        return UserRole.createCriteria().get {
            // Sets the join type for role property in order to get its name.
            createAlias("role", "role", JoinType.INNER_JOIN)

            // Sets the query condition.
            and {
                eq("accountGroup.id", accountGroupId)
                eq("user.id", userId)
                eq("role.authority", roleName)
            }

            // Specifies the property we need.
            projections {
                count("id")
            }
        } > 0
    }

    /**
     * Adds roles for user.
     * @param accountGroupId: The string of the account group.
     * @param userId: The string of the user.
     * @param roleIds: The string of role id list.
     */
    @Transactional
    void add(String accountGroupId, String userId, List<String> roleIds) {
        roleIds.each { String roleId ->
            new UserRole().with {
                accountGroup = AccountGroup.proxy(accountGroupId)
                user = User.proxy(userId)
                role = Role.proxy(roleId)
                save()
            }
        }
    }

    /**
     * Deletes user role by user id and role ids.
     * @param accountGroupId: The string of the account group.
     * @param userId: The string of user id.
     * @param roleIds: The string list of role ids.
     */
    @Transactional
    @CompileDynamic
    void delete(String accountGroupId, String userId, List<String> roleIds) {
        UserRole.where {
            accountGroup == AccountGroup.proxy(accountGroupId) &&
                user == User.proxy(userId) &&
                role in roleIds.collect { Role.proxy(it) }
        }.deleteAll()
    }
}